using System;
using System.Net.Sockets;

// TODO verify IDispose implementation

namespace Circa.Protocol.Irc {
    public class HttpProxyConnection: Connection {
        public string ProxyHostName { get; set; }
        public int ProxyPort { get; set; }

        public HttpProxyConnection( string _hostName, int _port, string _proxyHostName, int _proxyPort ): base( _hostName, _port ) {
            ProxyHostName = _proxyHostName;
            ProxyPort = _proxyPort;
        }

        public override void Connect( ) {
            sock = new TcpClient( );
            sock.BeginConnect( ProxyHostName, ProxyPort, new AsyncCallback( ProxyConnectCallback ), this );
        }

        private static void ProxyConnectCallback( IAsyncResult ar ) {
            ( (HttpProxyConnection) ar.AsyncState ).ProxyConnectDone( ar );
        }

        private void ProxyConnectDone( IAsyncResult ar ) {
            try {
                sock.EndConnect( ar );
            }
            catch ( Exception ex ) {
                OnConnectError( new NetworkErrorEventArgs( ex ) );
                return;
            }

            ns = sock.GetStream( );

            // XXX what with internationalized domain names either we're going to have to learn how to deal with IDNs ourselves or we're going to
            // have to start using UTF-8 here instead
            string request = string.Format( "CONNECT {0}:{1} HTTP/1.0\nUser-Agent: Circa-IRC\n\n", HostName, Port );
            System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding( );
            byte[] requestBytes = encoding.GetBytes( request );

            ConnectionIoState io = new ConnectionIoState { Buffer = new byte[ 4096 ], Connection = this };
            try {
                ns.Write( requestBytes, 0, requestBytes.Length );
                ns.BeginRead( io.Buffer, 0, io.Buffer.Length, new AsyncCallback( HandshakeReadCallback ), io );
            }
            catch ( Exception e ) {
                OnReadError( new NetworkErrorEventArgs( e ) );
            }
        }

        private static void HandshakeReadCallback( IAsyncResult ar ) {
            ConnectionIoState io = ( ConnectionIoState ) ar.AsyncState;
            ( ( HttpProxyConnection ) io.Connection ).HandshakeReadDone( ar, io );
        }

        private void HandshakeReadDone( IAsyncResult ar, ConnectionIoState io ) {
            int bytesRead = 0;
            try {
                bytesRead = ns.EndRead( ar );
            }
            catch ( Exception ex ) {
                OnReadError( new NetworkErrorEventArgs( ex ) );
            }
            if ( 0 == bytesRead ) {
                return;
            }

            // TODO read the HTTP response properly
            // TODO handle proxy authentication
            // TODO can we tell if the connection from the proxy to the destionation was rejected, timed out, etc.?
            System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding( );
            string response = encoding.GetString( io.Buffer, 0, bytesRead );
            if ( !response.StartsWith( "HTTP/1.0 200" ) ) {
                OnConnectError( new NetworkErrorEventArgs( new Exception( string.Format( "Error connecting via HTTP proxy. Proxy returned: {0}", response ) ) ) );
                return;
            }

            OnConnected( EventArgs.Empty );
            StartRead( );
        }

    }

}
